Completed
Pull Request — master (#99)
by
unknown
01:15
created

api_client.js ➔ APIClient   F

Complexity

Conditions 9
Paths 1281

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 9
c 2
b 1
f 0
nc 1281
dl 0
loc 44
rs 3
nop 1
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    BtccomConverter = require('./btccom.convert'),
11
    BlocktrailConverter = require('./blocktrail.convert'),
12
    RestClient = require('./rest_client'),
13
    Encryption = require('./encryption'),
14
    KeyDerivation = require('./keyderivation'),
15
    EncryptionMnemonic = require('./encryption_mnemonic'),
16
    blocktrail = require('./blocktrail'),
17
    randomBytes = require('randombytes'),
18
    CryptoJS = require('crypto-js'),
19
    webworkifier = require('./webworkifier');
20
21
var useWebWorker = require('./use-webworker')();
22
23
24
/**
25
 * helper to wrap a promise so that the callback get's called when it succeeds or fails
26
 *
27
 * @param promise   {q.Promise}
28
 * @param cb        function
29
 * @return q.Promise
30
 */
31
function callbackify(promise, cb) {
32
    // add a .then to trigger the cb for people using callbacks
33
    if (cb) {
34
        promise
35
            .then(function(res) {
36
                // use q.nextTick for asyncness
37
                q.nextTick(function() {
38
                    cb(null, res);
39
                });
40
            }, function(err) {
41
                // use q.nextTick for asyncness
42
                q.nextTick(function() {
43
                    cb(err, null);
44
                });
45
            });
46
    }
47
48
    // return the promise for people using promises
49
    return promise;
50
}
51
52
/**
53
 * Bindings to consume the BlockTrail API
54
 *
55
 * @param options       object{
56
 *                          apiKey: 'API_KEY',
57
 *                          apiSecret: 'API_SECRET',
58
 *                          host: 'defaults to api.blocktrail.com',
59
 *                          network: 'BTC|LTC',
60
 *                          testnet: true|false
61
 *                      }
62
 * @constructor
63
 */
64
var APIClient = function(options) {
65
    var self = this;
66
67
    // handle constructor call without 'new'
68
    if (!(this instanceof APIClient)) {
69
        return new APIClient(options);
70
    }
71
    self.bitcoinCash = options.network && options.network === "BCC";
72
    self.testnet = options.testnet = options.testnet || false;
73
    if (self.bitcoinCash) {
74
        if (self.testnet) {
75
            self.network = bitcoin.networks.bitcoincashtestnet;
76
        } else {
77
            self.network = bitcoin.networks.bitcoincash;
78
        }
79
    } else {
80
        if (self.testnet) {
81
            self.network = bitcoin.networks.testnet;
82
        } else {
83
            self.network = bitcoin.networks.bitcoin;
84
        }
85
    }
86
87
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
88
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
89
90
    options.apiNetwork = options.apiNetwork || ((self.testnet ? "t" : "") + (options.network || 'BTC').toUpperCase());
91
92
    if (typeof options.btccom === "undefined") {
93
        options.btccom = true;
94
    }
95
96
    /**
97
     * @type RestClient
98
     */
99
    self.client = APIClient.initRestClient(options);
100
101
    if (options.btccom) {
102
        self.converter = new BtccomConverter(self.network, true);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
103
    } else {
104
        self.converter = new BlocktrailConverter();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
105
    }
106
107
};
108
109
APIClient.initRestClient = function(options) {
110
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
111
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
112
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
113
    }
114
115
    // trim off leading https?://
116
    if (options.host && options.host.indexOf("https://") === 0) {
117
        options.https = true;
118
        options.host = options.host.substr(8);
119
    } else if (options.host && options.host.indexOf("http://") === 0) {
120
        options.https = false;
121
        options.host = options.host.substr(7);
122
    }
123
124
    if (typeof options.https === "undefined") {
125
        options.https = true;
126
    }
127
128
    if (!options.port) {
129
        options.port = options.https ? 443 : 80;
130
    }
131
132
    if (options.btccom) {
133
        if (!options.host) {
134
            options.host = 'chain.api.btc.com';
135
        }
136
137
        if (!options.endpoint) {
138
            options.endpoint = "/" + (options.apiVersion || "v3");
139
        }
140
141
    } else {
142
        if (!options.host) {
143
            options.host = 'api.blocktrail.com';
144
        }
145
146
        if (!options.endpoint) {
147
            options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
148
        }
149
    }
150
151
    return new RestClient(options);
152
};
153
154
var determineDataStorageV2_3 = function(options) {
155
    return q.when(options)
156
        .then(function(options) {
157
            // legacy
158
            if (options.storePrimaryMnemonic) {
159
                options.storeDataOnServer = options.storePrimaryMnemonic;
160
            }
161
162
            // storeDataOnServer=false when primarySeed is provided
163
            if (typeof options.storeDataOnServer === "undefined") {
164
                options.storeDataOnServer = !options.primarySeed;
165
            }
166
167
            return options;
168
        });
169
};
170
171
var produceEncryptedDataV2 = function(options, notify) {
172
    return q.when(options)
173
        .then(function(options) {
174
            if (options.storeDataOnServer) {
175
                if (!options.secret) {
176
                    if (!options.passphrase) {
177
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
178
                    }
179
180
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
181
182
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
183
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
184
                }
185
186
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
187
188
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
189
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
190
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
191
192
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
193
194
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
195
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
196
            }
197
198
            return options;
199
        });
200
};
201
202
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
203
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
204
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
205
        var saltBuf = Encryption.generateSalt();
206
        var iv = Encryption.generateIV();
207
208
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
209
            return require('./webworker');
210
        }, onLoadWorkerLoadAsmCrypto, {
211
            method: 'Encryption.encryptWithSaltAndIV',
212
            pt: pt,
213
            pw: pw,
214
            saltBuf: saltBuf,
215
            iv: iv,
216
            iterations: iter
217
        })
218
            .then(function(data) {
219
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
220
            });
221
    } else {
222
        try {
223
            return q.when(Encryption.encrypt(pt, pw, iter));
224
        } catch (e) {
225
            return q.reject(e);
226
        }
227
    }
228
};
229
230
APIClient.prototype.promisedDecrypt = function(ct, pw) {
231
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
232
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
233
            return require('./webworker');
234
        }, onLoadWorkerLoadAsmCrypto, {
235
            method: 'Encryption.decrypt',
236
            ct: ct,
237
            pw: pw
238
        })
239
            .then(function(data) {
240
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
241
            });
242
    } else {
243
        try {
244
            return q.when(Encryption.decrypt(ct, pw));
245
        } catch (e) {
246
            return q.reject(e);
247
        }
248
    }
249
};
250
251
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
252
    var self = this;
253
254
    return q.when(options)
255
        .then(function(options) {
256
            if (options.storeDataOnServer) {
257
                return q.when()
258
                    .then(function() {
259
                        if (!options.secret) {
260
                            if (!options.passphrase) {
261
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
262
                            }
263
264
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
265
266
                            // -> now a buffer
267
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
268
269
                            // -> now a buffer
270
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
271
                                .then(function(encryptedSecret) {
272
                                    options.encryptedSecret = encryptedSecret;
273
                                });
274
                        } else {
275
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
276
                                throw new Error('Secret must be a buffer');
277
                            }
278
                        }
279
                    })
280
                    .then(function() {
281
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
282
283
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
284
                            .then(function(encryptedPrimarySeed) {
285
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
286
                            });
287
                    })
288
                    .then(function() {
289
                        // skip generating recovery secret when explicitly set to false
290
                        if (options.recoverySecret === false) {
291
                            return;
292
                        }
293
294
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
295
                        if (!options.recoverySecret) {
296
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
297
                        }
298
299
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
300
                            .then(function(recoveryEncryptedSecret) {
301
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
302
                            });
303
                    })
304
                    .then(function() {
305
                        return options;
306
                    });
307
            } else {
308
                return options;
309
            }
310
        });
311
};
312
313
var doRemainingWalletDataV2_3 = function(options, network, notify) {
314
    return q.when(options)
315
        .then(function(options) {
316
            if (!options.backupPublicKey) {
317
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
318
            }
319
320
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
321
322
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
323
324
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
325
326
            if (!options.backupPublicKey) {
327
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
328
                options.backupPublicKey = options.backupPrivateKey.neutered();
329
            }
330
331
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
332
333
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
334
335
            return options;
336
        });
337
};
338
339
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
340
    var self = this;
341
342
    var deferred = q.defer();
343
    deferred.promise.spreadNodeify(cb);
344
345
    deferred.resolve(q.fcall(function() {
346
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
347
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
348
        });
349
    }));
350
351
    return deferred.promise;
352
};
353
354
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
355
    var self = this;
356
357
    if (useWebWorker) {
358
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
359
            return require('./webworker');
360
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
361
            .then(function(data) {
362
                return data.seed;
363
            });
364
    } else {
365
        try {
366
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
367
        } catch (e) {
368
            return q.reject(e);
369
        }
370
    }
371
};
372
373
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
374
    var self = this;
375
376
    var deferred = q.defer();
377
    deferred.promise.nodeify(cb);
378
379
    try {
380
        // avoid conflicting options
381
        if (options.passphrase && options.password) {
382
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
383
        }
384
        // normalize passphrase/password
385
        options.passphrase = options.passphrase || options.password;
386
        delete options.password;
387
388
        // avoid conflicting options
389
        if (options.primaryMnemonic && options.primarySeed) {
390
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
391
        }
392
393
        // avoid deprecated options
394
        if (options.primaryPrivateKey) {
395
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
396
        }
397
398
        // make sure we have at least one thing to use
399
        if (!options.primaryMnemonic && !options.primarySeed) {
400
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
401
        }
402
403
        if (options.primarySeed) {
404
            self.primarySeed = options.primarySeed;
405
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
406
            deferred.resolve(options);
407
        } else {
408
            if (!options.passphrase) {
409
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
410
            }
411
412
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
413
                .then(function(seedHex) {
414
                    try {
415
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
416
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
417
                        deferred.resolve(options);
418
                    } catch (e) {
419
                        deferred.reject(e);
420
                    }
421
                }, function(e) {
422
                    deferred.reject(e);
423
                });
424
        }
425
    } catch (e) {
426
        deferred.reject(e);
427
    }
428
429
    return deferred.promise;
430
};
431
432
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
433
    var self = this;
434
435
    var deferred = q.defer();
436
    deferred.promise.nodeify(cb);
437
438
    try {
439
        // avoid conflicting options
440
        if (options.backupMnemonic && options.backupPublicKey) {
441
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
442
        }
443
444
        // make sure we have at least one thing to use
445
        if (!options.backupMnemonic && !options.backupPublicKey) {
446
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
447
        }
448
449
        if (options.backupPublicKey) {
450
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
451
                deferred.resolve(options);
452
            } else {
453
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
454
                deferred.resolve(options);
455
            }
456
        } else {
457
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
458
                options.backupPublicKey = backupPrivateKey.neutered();
459
                deferred.resolve(options);
460
            }, function(e) {
461
                deferred.reject(e);
462
            });
463
        }
464
    } catch (e) {
465
        deferred.reject(e);
466
    }
467
468
    return deferred.promise;
469
};
470
471
APIClient.prototype.debugAuth = function(cb) {
472
    var self = this;
473
474
    return self.client.get("/debug/http-signature", null, true, cb);
475
};
476
477
/**
478
 * get a single address
479
 *
480
 * @param address      string       address hash
481
 * @param [cb]          function    callback function to call when request is complete
482
 * @return q.Promise
483
 */
484
APIClient.prototype.address = function(address, cb) {
485
    var self = this;
486
    // @TODO: Hit by the rate limit for btc.com - Fix in progress
487
    return callbackify(self.client.get(self.converter.getUrlForAddress(address), null)
488
        .then(function(data) {
489
            return self.converter.handleErros(self, data);
490
        })
491
        .then(function(data) {
492
            if (data === null) {
493
                return data;
494
            } else {
495
                return self.converter.convertAddress(data);
496
            }
497
        }), cb);
498
};
499
500
APIClient.prototype.addresses = function(addresses, cb) {
501
    var self = this;
502
503
    return callbackify(self.client.post("/address", null, {"addresses": addresses}), cb);
504
};
505
506
507
/**
508
 * get all transactions for an address (paginated)
509
 *
510
 * @param address       string      address hash
511
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
512
 * @param [cb]          function    callback function to call when request is complete
513
 * @return q.Promise
514
 */
515
APIClient.prototype.addressTransactions = function(address, params, cb) {
516
517
    var self = this;
518
519
    if (typeof params === "function") {
520
        cb = params;
521
        params = null;
522
    }
523
524
    return callbackify(self.client.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
525
        .then(function(data) {
526
            return self.converter.handleErros(self, data);
527
        })
528
        .then(function(data) {
529
            return data.data === null ? data : self.converter.convertAddressTxs(data);
530
        }), cb);
531
};
532
533
/**
534
 * get all transactions for a batch of addresses (paginated)
535
 *
536
 * @param addresses     array       address hashes
537
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
538
 * @param [cb]          function    callback function to call when request is complete
539
 * @return q.Promise
540
 */
541
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
542
    var self = this;
543
544
    if (typeof params === "function") {
545
        cb = params;
546
        params = null;
547
    }
548
549
    return self.client.post("/address/has-transactions", self.converter.paginationParams(params), {"addresses": addresses}, cb);
550
};
551
552
/**
553
 * get all unconfirmed transactions for an address (paginated)
554
 *
555
 * @param address       string      address hash
556
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
557
 * @param [cb]          function    callback function to call when request is complete
558
 * @return q.Promise
559
 */
560
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
561
    var self = this;
562
563
    if (typeof params === "function") {
564
        cb = params;
565
        params = null;
566
    }
567
568
    return callbackify(self.client.get(self.converter.getUrlForAddressTransactions(address), self.converter.paginationParams(params))
569
        .then(function(data) {
570
            return self.converter.handleErros(self, data);
571
        })
572
        .then(function(data) {
573
            if (data.data === null) {
574
                return data;
575
            }
576
577
            var res = self.converter.convertAddressTxs(data);
578
            res.data = res.data.filter(function(tx) {
579
                return !tx.confirmations;
580
            });
581
582
            return res;
583
        }), cb);
584
};
585
586
/**
587
 * get all unspent outputs for an address (paginated)
588
 *
589
 * @param address       string      address hash
590
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
591
 * @param [cb]          function    callback function to call when request is complete
592
 * @return q.Promise
593
 */
594
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
595
    var self = this;
596
597
    if (typeof params === "function") {
598
        cb = params;
599
        params = null;
600
    }
601
602
    return callbackify(self.client.get(self.converter.getUrlForAddressUnspent(address), self.converter.paginationParams(params))
603
        .then(function(data) {
604
            return self.converter.handleErros(self, data);
605
        })
606
        .then(function(data) {
607
            return data.data === null ? data : self.converter.convertAddressUnspentOutputs(data, address);
608
        }), cb);
609
};
610
611
/**
612
 * get all unspent outputs for a batch of addresses (paginated)
613
 *
614
 * @param addresses     array       address hashes
615
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
616
 * @param [cb]          function    callback function to call when request is complete
617
 * @return q.Promise
618
 */
619
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
620
    var self = this;
621
622
    if (self.converter instanceof BtccomConverter) {
623
        throw new Error("Not implemented");
624
    }
625
626
    if (typeof params === "function") {
627
        cb = params;
628
        params = null;
629
    }
630
631
    return callbackify(self.client.post("/address/unspent-outputs", params, {"addresses": addresses}), cb);
632
};
633
634
/**
635
 * verify ownership of an address
636
 *
637
 * @param address       string      address hash
638
 * @param signature     string      a signed message (the address hash) using the private key of the address
639
 * @param [cb]          function    callback function to call when request is complete
640
 * @return q.Promise
641
 */
642
APIClient.prototype.verifyAddress = function(address, signature, cb) {
643
    var self = this;
644
    // @TODO:
645
646
    return self.client.post("/address/" + address + "/verify", null, {signature: signature}, cb);
647
};
648
649
/**
650
 *
651
 * get all blocks (paginated)
652
 * ASK
653
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
654
 * @param [cb]          function    callback function to call when request is complete
655
 * @return q.Promise
656
 */
657
APIClient.prototype.allBlocks = function(params, cb) {
658
    var self = this;
659
660
    if (self.converter instanceof BtccomConverter) {
661
        throw new Error("Not implemented");
662
    }
663
664
    if (typeof params === "function") {
665
        cb = params;
666
        params = null;
667
    }
668
669
    return callbackify(self.client.get(self.converter.getUrlForAllBlocks(), self.converter.paginationParams(params)), cb);
670
};
671
672
/**
673
 * get a block
674
 *
675
 * @param block         string|int  a block hash or a block height
676
 * @param [cb]          function    callback function to call when request is complete
677
 * @return q.Promise
678
 */
679
APIClient.prototype.block = function(block, cb) {
680
    var self = this;
681
682
    return callbackify(self.client.get(self.converter.getUrlForBlock(block), null)
683
        .then(function(data) {
684
            return self.converter.handleErros(self, data);
685
        })
686
        .then(function(data) {
687
            return data.data === null ? data : self.converter.convertBlock(data);
688
        }), cb);
689
};
690
691
/**
692
 * TODO btc.com
693
 * get the latest block
694
 *
695
 * @param [cb]          function    callback function to call when request is complete
696
 * @return q.Promise
697
 */
698
APIClient.prototype.blockLatest = function(cb) {
699
    var self = this;
700
701
    return callbackify(self.client.get(self.converter.getUrlForBlock("latest"), null)
702
        .then(function(data) {
703
            return self.converter.handleErros(self, data);
704
        })
705
        .then(function(data) {
706
            return data.data === null ? data : self.converter.convertBlock(data);
707
        }), cb);
708
};
709
710
/**
711
 * get all transactions for a block (paginated)
712
 *
713
 * @param block         string|int  a block hash or a block height
714
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
715
 * @param [cb]          function    callback function to call when request is complete
716
 * @return q.Promise
717
 */
718
APIClient.prototype.blockTransactions = function(block, params, cb) {
719
    var self = this;
720
721
    if (typeof params === "function") {
722
        cb = params;
723
        params = null;
724
    }
725
726
    return callbackify(self.client.get(self.converter.getUrlForBlockTransaction(block), self.converter.paginationParams(params))
727
        .then(function(data) {
728
            return self.converter.handleErros(self, data);
729
        })
730
        .then(function(data) {
731
            return data.data ===  null ? data : self.converter.convertBlockTxs(data);
732
        }), cb);
733
};
734
735
/**
736
 * get a single transaction
737
 *
738
 * @param tx            string      transaction hash
739
 * @param [cb]          function    callback function to call when request is complete
740
 * @return q.Promise
741
 */
742
APIClient.prototype.transaction = function(tx, cb) {
743
    var self = this;
744
745
    return callbackify(self.client.get(self.converter.getUrlForTransaction(tx), null)
746
        .then(function(data) {
747
            return self.converter.handleErros(self, data);
748
        })
749
        .then(function(data) {
750
            if (data.data === null) {
751
                return data;
752
            } else {
753
                // for BTC.com API we need to fetch the raw hex from the BTC.com explorer endpoint
754
                if (self.converter instanceof BtccomConverter) {
755
                    var txPath = data.data.hash + ".rawhex";
756
                    return self.converter.specialRawTxClientOnly.get(txPath, null, false)
757
                        .then(function(rawTx) {
758
                            return [data, rawTx];
759
                        })
760
                        .then(function(dataAndTx) {
761
                            if (dataAndTx !== null) {
762
                                var data = dataAndTx[0];
763
                                var rawTx = dataAndTx[1];
764
                                return self.converter.convertTx(data, rawTx);
765
                            } else {
766
                                return dataAndTx;
767
                            }
768
                        });
769
                } else {
770
                    return self.converter.convertTx(data);
771
                }
772
            }
773
        }), cb);
774
};
775
776
/**
777
 * get a batch of transactions
778
 *
779
 * @param txs           string[]    list of transaction hashes (txId)
780
 * @param [cb]          function    callback function to call when request is complete
781
 * @return q.Promise
782
 */
783
APIClient.prototype.transactions = function(txs, cb) {
784
    var self = this;
785
786
    if (self.converter instanceof BtccomConverter) {
787
        return callbackify(self.client.get(self.converter.getUrlForTransactions(txs), null)
788
            .then(function(data) {
789
                return self.converter.handleErros(self, data);
790
            })
791
            .then(function(data) {
792
                if (data.data === null) {
793
                    return data;
794
                } else {
795
                    return self.converter.convertTxs(data);
796
                }
797
            }), cb);
798
    } else {
799
        return callbackify(self.client.post("/transactions", null, txs, null, false), cb);
800
    }
801
};
802
803
/**
804
 * get a paginated list of all webhooks associated with the api user
805
 *
806
 * @param [params]      object      pagination: {page: 1, limit: 20}
807
 * @param [cb]          function    callback function to call when request is complete
808
 * @return q.Promise
809
 */
810
APIClient.prototype.allWebhooks = function(params, cb) {
811
    var self = this;
812
813
    if (typeof params === "function") {
814
        cb = params;
815
        params = null;
816
    }
817
818
    return self.client.get("/webhooks", params, cb);
819
};
820
821
/**
822
 * create a new webhook
823
 *
824
 * @param url           string      the url to receive the webhook events
825
 * @param [identifier]  string      a unique identifier associated with the webhook
826
 * @param [cb]          function    callback function to call when request is complete
827
 * @return q.Promise
828
 */
829
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
830
    var self = this;
831
832
    if (typeof identifier === "function") {
833
        //mimic function overloading
834
        cb = identifier;
835
        identifier = null;
836
    }
837
838
    return self.client.post("/webhook", null, {url: url, identifier: identifier}, cb);
839
};
840
841
/**
842
 * Converts a cash address to the legacy (base58) format
843
 * @param {string} input
844
 * @returns {string}
845
 */
846
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
847
    if (this.network === bitcoin.networks.bitcoincash || this.network === bitcoin.networks.bitcoincashtestnet) {
848
        var address;
849
        try {
850
            bitcoin.address.fromBase58Check(input, this.network);
851
            return input;
852
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
853
854
        address = bitcoin.address.fromCashAddress(input, this.network);
855
        var prefix;
856
        if (address.version === bitcoin.script.types.P2PKH) {
857
            prefix = this.network.pubKeyHash;
858
        } else if (address.version === bitcoin.script.types.P2SH) {
859
            prefix = this.network.scriptHash;
860
        } else {
861
            throw new Error("Unsupported address type");
862
        }
863
864
        return bitcoin.address.toBase58Check(address.hash, prefix);
865
    }
866
867
    throw new Error("Cash addresses only work on bitcoin cash");
868
};
869
870
/**
871
 * Converts a legacy bitcoin to the new cashaddr format
872
 * @param {string} input
873
 * @returns {string}
874
 */
875
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
876
    if (this.network === bitcoin.networks.bitcoincash || this.network === bitcoin.networks.bitcoincashtestnet) {
877
        var address;
878
        try {
879
            bitcoin.address.fromCashAddress(input, this.network);
880
            return input;
881
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
882
883
        address = bitcoin.address.fromBase58Check(input, this.network);
884
        var scriptType;
885
        if (address.version === this.network.pubKeyHash) {
886
            scriptType = bitcoin.script.types.P2PKH;
887
        } else if (address.version === this.network.scriptHash) {
888
            scriptType = bitcoin.script.types.P2SH;
889
        } else {
890
            throw new Error("Unsupported address type");
891
        }
892
893
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
894
    }
895
896
    throw new Error("Cash addresses only work on bitcoin cash");
897
};
898
899
/**
900
 * get an existing webhook by it's identifier
901
 *
902
 * @param identifier    string      the unique identifier of the webhook to get
903
 * @param [cb]          function    callback function to call when request is complete
904
 * @return q.Promise
905
 */
906
APIClient.prototype.getWebhook = function(identifier, cb) {
907
    var self = this;
908
909
    return self.client.get("/webhook/" + identifier, null, cb);
910
};
911
912
/**
913
 * update an existing webhook
914
 *
915
 * @param identifier    string      the unique identifier of the webhook
916
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
917
 * @param [cb]          function    callback function to call when request is complete
918
 * @return q.Promise
919
 */
920
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
921
    var self = this;
922
923
    return self.client.put("/webhook/" + identifier, null, webhookData, cb);
924
};
925
926
/**
927
 * deletes an existing webhook and any event subscriptions associated with it
928
 *
929
 * @param identifier    string      the unique identifier of the webhook
930
 * @param [cb]          function    callback function to call when request is complete
931
 * @return q.Promise
932
 */
933
APIClient.prototype.deleteWebhook = function(identifier, cb) {
934
    var self = this;
935
936
    return self.client.delete("/webhook/" + identifier, null, null, cb);
937
};
938
939
/**
940
 * get a paginated list of all the events a webhook is subscribed to
941
 *
942
 * @param identifier    string      the unique identifier of the webhook
943
 * @param [params]      object      pagination: {page: 1, limit: 20}
944
 * @param [cb]          function    callback function to call when request is complete
945
 * @return q.Promise
946
 */
947
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
948
    var self = this;
949
950
    if (typeof params === "function") {
951
        cb = params;
952
        params = null;
953
    }
954
955
    return self.client.get("/webhook/" + identifier + "/events", params, cb);
956
};
957
958
/**
959
 * subscribes a webhook to transaction events for a particular transaction
960
 *
961
 * @param identifier    string      the unique identifier of the webhook
962
 * @param transaction   string      the transaction hash
963
 * @param confirmations integer     the amount of confirmations to send
964
 * @param [cb]          function    callback function to call when request is complete
965
 * @return q.Promise
966
 */
967
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
968
    var self = this;
969
    var postData = {
970
        'event_type': 'transaction',
971
        'transaction': transaction,
972
        'confirmations': confirmations
973
    };
974
975
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
976
};
977
978
/**
979
 * subscribes a webhook to transaction events on a particular address
980
 *
981
 * @param identifier    string      the unique identifier of the webhook
982
 * @param address       string      the address hash
983
 * @param confirmations integer     the amount of confirmations to send
984
 * @param [cb]          function    callback function to call when request is complete
985
 * @return q.Promise
986
 */
987
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
988
    var self = this;
989
    var postData = {
990
        'event_type': 'address-transactions',
991
        'address': address,
992
        'confirmations': confirmations
993
    };
994
995
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
996
};
997
998
/**
999
 * batch subscribes a webhook to multiple transaction events
1000
 *
1001
 * @param  identifier   string      the unique identifier of the webhook
1002
 * @param  batchData    array       An array of objects containing batch event data:
1003
 *                                  {address : 'address', confirmations : 'confirmations']
1004
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
1005
 * @param [cb]          function    callback function to call when request is complete
1006
 * @return q.Promise
1007
 */
1008
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
1009
    var self = this;
1010
    batchData.forEach(function(record) {
1011
        record.event_type = 'address-transactions';
1012
    });
1013
1014
    return self.client.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
1015
};
1016
1017
/**
1018
 * subscribes a webhook to a new block event
1019
 *
1020
 * @param identifier    string      the unique identifier of the webhook
1021
 * @param [cb]          function    callback function to call when request is complete
1022
 * @return q.Promise
1023
 */
1024
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
1025
    var self = this;
1026
    var postData = {
1027
        'event_type': 'block'
1028
    };
1029
1030
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
1031
};
1032
1033
/**
1034
 * removes an address transaction event subscription from a webhook
1035
 *
1036
 * @param identifier    string      the unique identifier of the webhook
1037
 * @param address       string      the address hash
1038
 * @param [cb]          function    callback function to call when request is complete
1039
 * @return q.Promise
1040
 */
1041
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
1042
    var self = this;
1043
1044
    return self.client.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
1045
};
1046
1047
/**
1048
 * removes an transaction event subscription from a webhook
1049
 *
1050
 * @param identifier    string      the unique identifier of the webhook
1051
 * @param transaction   string      the transaction hash
1052
 * @param [cb]          function    callback function to call when request is complete
1053
 * @return q.Promise
1054
 */
1055
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
1056
    var self = this;
1057
1058
    return self.client.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
1059
};
1060
1061
/**
1062
 * removes a block event subscription from a webhook
1063
 *
1064
 * @param identifier    string      the unique identifier of the webhook
1065
 * @param [cb]          function    callback function to call when request is complete
1066
 * @return q.Promise
1067
 */
1068
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
1069
    var self = this;
1070
1071
    return self.client.delete("/webhook/" + identifier + "/block", null, null, cb);
1072
};
1073
1074
/**
1075
 * initialize an existing wallet
1076
 *
1077
 * Either takes two argument:
1078
 * @param options       object      {}
1079
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
1080
 *
1081
 * Or takes three arguments (old, deprecated syntax):
1082
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1083
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1084
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1079. The second definition is ignored.
Loading history...
1085
 *
1086
 * @returns {q.Promise}
1087
 */
1088
APIClient.prototype.initWallet = function(options, cb) {
1089
    var self = this;
1090
1091
    if (typeof options !== "object") {
1092
        // get the old-style arguments
1093
        options = {
1094
            identifier: arguments[0],
1095
            passphrase: arguments[1]
1096
        };
1097
1098
        cb = arguments[2];
1099
    }
1100
1101
    if (options.check_backup_key) {
1102
        if (typeof options.check_backup_key !== "string") {
1103
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1104
        }
1105
    }
1106
1107
    var deferred = q.defer();
1108
    deferred.promise.spreadNodeify(cb);
1109
1110
    var identifier = options.identifier;
1111
1112
    if (!identifier) {
1113
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1114
        return deferred.promise;
1115
    }
1116
1117
    deferred.resolve(self.client.get("/wallet/" + identifier, null, true).then(function(result) {
1118
        var keyIndex = options.keyIndex || result.key_index;
1119
1120
        options.walletVersion = result.wallet_version;
1121
1122
        if (options.check_backup_key) {
1123
            if (options.check_backup_key !== result.backup_public_key[0]) {
1124
                throw new Error("Backup key returned from server didn't match our own copy");
1125
            }
1126
        }
1127
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1128
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1129
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1130
        });
1131
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1132
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1133
        });
1134
1135
        // initialize wallet
1136
        var wallet = new Wallet(
1137
            self,
1138
            identifier,
1139
            options.walletVersion,
1140
            result.primary_mnemonic,
1141
            result.encrypted_primary_seed,
1142
            result.encrypted_secret,
1143
            primaryPublicKeys,
1144
            backupPublicKey,
1145
            blocktrailPublicKeys,
1146
            keyIndex,
1147
            result.segwit || 0,
1148
            self.testnet,
1149
            result.checksum,
1150
            result.upgrade_key_index,
1151
            options.useCashAddress,
1152
            options.bypassNewAddressCheck
1153
        );
1154
1155
        wallet.recoverySecret = result.recovery_secret;
1156
1157
        if (!options.readOnly) {
1158
            return wallet.unlock(options).then(function() {
1159
                return wallet;
1160
            });
1161
        } else {
1162
            return wallet;
1163
        }
1164
    }));
1165
1166
    return deferred.promise;
1167
};
1168
1169
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1170
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1171
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1172
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1173
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1174
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1175
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1176
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1177
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1178
1179
/**
1180
 * create a new wallet
1181
 *   - will generate a new primary seed and backup seed
1182
 *
1183
 * Either takes two argument:
1184
 * @param options       object      {}
1185
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) // nocommit @TODO
1186
 *
1187
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1188
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1184. The second definition is ignored.
Loading history...
1189
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1185. The second definition is ignored.
Loading history...
1190
 *
1191
 * Or takes four arguments (old, deprecated syntax):
1192
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1193
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1194
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1195
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1185. The second definition is ignored.
Loading history...
1196
 * @returns {q.Promise}
1197
 */
1198
APIClient.prototype.createNewWallet = function(options, cb) {
1199
    /* jshint -W071, -W074 */
1200
1201
    var self = this;
1202
1203
    if (typeof options !== "object") {
1204
        // get the old-style arguments
1205
        var identifier = arguments[0];
1206
        var passphrase = arguments[1];
1207
        var keyIndex = arguments[2];
1208
        cb = arguments[3];
1209
1210
        // keyIndex is optional
1211
        if (typeof keyIndex === "function") {
1212
            cb = keyIndex;
1213
            keyIndex = null;
1214
        }
1215
1216
        options = {
1217
            identifier: identifier,
1218
            passphrase: passphrase,
1219
            keyIndex: keyIndex
1220
        };
1221
    }
1222
1223
    // default to v3
1224
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1225
1226
    var deferred = q.defer();
1227
    deferred.promise.spreadNodeify(cb);
1228
1229
    q.nextTick(function() {
1230
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1231
1232
        options.keyIndex = options.keyIndex || 0;
1233
        options.passphrase = options.passphrase || options.password;
1234
        delete options.password;
1235
1236
        if (!options.identifier) {
1237
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1238
            return deferred.promise;
1239
        }
1240
1241
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1242
            self._createNewWalletV1(options)
1243
                .progress(function(p) { deferred.notify(p); })
1244
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1245
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1246
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1247
            self._createNewWalletV2(options)
1248
                .progress(function(p) { deferred.notify(p); })
1249
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1250
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1251
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1252
            self._createNewWalletV3(options)
1253
                .progress(function(p) { deferred.notify(p); })
1254
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1255
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1256
        } else {
1257
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1258
        }
1259
    });
1260
1261
    return deferred.promise;
1262
};
1263
1264
APIClient.prototype._createNewWalletV1 = function(options) {
1265
    var self = this;
1266
1267
    var deferred = q.defer();
1268
1269
    q.nextTick(function() {
1270
1271
        if (!options.primaryMnemonic && !options.primarySeed) {
1272
            if (!options.passphrase && !options.password) {
1273
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1274
                return deferred.promise;
1275
            } else {
1276
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1277
                if (options.storePrimaryMnemonic !== false) {
1278
                    options.storePrimaryMnemonic = true;
1279
                }
1280
            }
1281
        }
1282
1283
        if (!options.backupMnemonic && !options.backupPublicKey) {
1284
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1285
        }
1286
1287
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1288
1289
        self.resolvePrimaryPrivateKeyFromOptions(options)
1290
            .then(function(options) {
1291
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1292
1293
                return self.resolveBackupPublicKeyFromOptions(options)
1294
                    .then(function(options) {
1295
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1296
1297
                        // create a checksum of our private key which we'll later use to verify we used the right password
1298
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1299
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1300
                        var keyIndex = options.keyIndex;
1301
1302
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1303
1304
                        // send the public keys to the server to store them
1305
                        //  and the mnemonic, which is safe because it's useless without the password
1306
                        return self.storeNewWalletV1(
1307
                            options.identifier,
1308
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1309
                            [options.backupPublicKey.toBase58(), "M"],
1310
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1311
                            checksum,
1312
                            keyIndex,
1313
                            options.segwit || null
1314
                        )
1315
                            .then(function(result) {
1316
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1317
1318
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1319
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1320
                                });
1321
1322
                                var wallet = new Wallet(
1323
                                    self,
1324
                                    options.identifier,
1325
                                    Wallet.WALLET_VERSION_V1,
1326
                                    options.primaryMnemonic,
1327
                                    null,
1328
                                    null,
1329
                                    {keyIndex: primaryPublicKey},
1330
                                    options.backupPublicKey,
1331
                                    blocktrailPublicKeys,
1332
                                    keyIndex,
1333
                                    result.segwit || 0,
1334
                                    self.testnet,
1335
                                    checksum,
1336
                                    result.upgrade_key_index,
1337
                                    options.useCashAddress,
1338
                                    options.bypassNewAddressCheck
1339
                                );
1340
1341
                                return wallet.unlock({
1342
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1343
                                    passphrase: options.passphrase,
1344
                                    primarySeed: options.primarySeed,
1345
                                    primaryMnemonic: null // explicit null
1346
                                }).then(function() {
1347
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1348
                                    return [
1349
                                        wallet,
1350
                                        {
1351
                                            walletVersion: wallet.walletVersion,
1352
                                            primaryMnemonic: options.primaryMnemonic,
1353
                                            backupMnemonic: options.backupMnemonic,
1354
                                            blocktrailPublicKeys: blocktrailPublicKeys
1355
                                        }
1356
                                    ];
1357
                                });
1358
                            });
1359
                    }
1360
                );
1361
            })
1362
            .then(
1363
            function(r) {
1364
                deferred.resolve(r);
1365
            },
1366
            function(e) {
1367 View Code Duplication
                deferred.reject(e);
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1368
            }
1369
        )
1370
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1371
    });
1372
1373
    return deferred.promise;
1374
};
1375
1376
APIClient.prototype._createNewWalletV2 = function(options) {
1377
    var self = this;
1378
1379
    var deferred = q.defer();
1380
1381
    // avoid modifying passed options
1382
    options = _.merge({}, options);
1383
1384
    determineDataStorageV2_3(options)
1385
        .then(function(options) {
1386
            options.passphrase = options.passphrase || options.password;
1387
            delete options.password;
1388
1389
            // avoid deprecated options
1390
            if (options.primaryPrivateKey) {
1391
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1392
            }
1393
1394
            // seed should be provided or generated
1395
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1396
1397
            return options;
1398
        })
1399
        .then(function(options) {
1400
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1401
        })
1402
        .then(function(options) {
1403
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1404
        })
1405
        .then(function(options) {
1406
            // create a checksum of our private key which we'll later use to verify we used the right password
1407
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1408
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1409
            var keyIndex = options.keyIndex;
1410
1411
            // send the public keys and encrypted data to server
1412
            return self.storeNewWalletV2(
1413
                options.identifier,
1414
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1415
                [options.backupPublicKey.toBase58(), "M"],
1416
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1417
                options.storeDataOnServer ? options.encryptedSecret : false,
1418
                options.storeDataOnServer ? options.recoverySecret : false,
1419
                checksum,
1420
                keyIndex,
1421
                options.support_secret || null,
1422
                options.segwit || null
1423
            )
1424
                .then(
1425
                function(result) {
1426
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1427
1428
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1429
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1430
                    });
1431
1432
                    var wallet = new Wallet(
1433
                        self,
1434
                        options.identifier,
1435
                        Wallet.WALLET_VERSION_V2,
1436
                        null,
1437
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1438
                        options.storeDataOnServer ? options.encryptedSecret : null,
1439
                        {keyIndex: options.primaryPublicKey},
1440
                        options.backupPublicKey,
1441
                        blocktrailPublicKeys,
1442
                        keyIndex,
1443
                        result.segwit || 0,
1444
                        self.testnet,
1445
                        checksum,
1446
                        result.upgrade_key_index,
1447
                        options.useCashAddress,
1448
                        options.bypassNewAddressCheck
1449
                    );
1450
1451
                    // pass along decrypted data to avoid extra work
1452
                    return wallet.unlock({
1453
                        walletVersion: Wallet.WALLET_VERSION_V2,
1454
                        passphrase: options.passphrase,
1455
                        primarySeed: options.primarySeed,
1456
                        secret: options.secret
1457
                    }).then(function() {
1458
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1459
                        return [
1460
                            wallet,
1461
                            {
1462
                                walletVersion: wallet.walletVersion,
1463
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1464
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1465
                                    null,
1466
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1467
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1468
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1469
                                    null,
1470
                                encryptedSecret: options.encryptedSecret ?
1471
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1472
                                    null,
1473
                                blocktrailPublicKeys: blocktrailPublicKeys
1474
                            }
1475
                        ];
1476 View Code Duplication
                    });
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1477
                }
1478
            );
1479
        })
1480
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1481
1482
    return deferred.promise;
1483
};
1484
1485
APIClient.prototype._createNewWalletV3 = function(options) {
1486
    var self = this;
1487
1488
    var deferred = q.defer();
1489
1490
    // avoid modifying passed options
1491
    options = _.merge({}, options);
1492
1493
    determineDataStorageV2_3(options)
1494
        .then(function(options) {
1495
            options.passphrase = options.passphrase || options.password;
1496
            delete options.password;
1497
1498
            // avoid deprecated options
1499
            if (options.primaryPrivateKey) {
1500
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1501
            }
1502
1503
            // seed should be provided or generated
1504
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1505
1506
            return options;
1507
        })
1508
        .then(function(options) {
1509
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1510
        })
1511
        .then(function(options) {
1512
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1513
        })
1514
        .then(function(options) {
1515
            // create a checksum of our private key which we'll later use to verify we used the right password
1516
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1517
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1518
            var keyIndex = options.keyIndex;
1519
1520
            // send the public keys and encrypted data to server
1521
            return self.storeNewWalletV3(
1522
                options.identifier,
1523
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1524
                [options.backupPublicKey.toBase58(), "M"],
1525
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1526
                options.storeDataOnServer ? options.encryptedSecret : false,
1527
                options.storeDataOnServer ? options.recoverySecret : false,
1528
                checksum,
1529
                keyIndex,
1530
                options.support_secret || null,
1531
                options.segwit || null
1532
            )
1533
                .then(
1534
                    // result, deferred, self(apiclient)
1535
                    function(result) {
1536
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1537
1538
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1539
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1540
                        });
1541
1542
                        var wallet = new Wallet(
1543
                            self,
1544
                            options.identifier,
1545
                            Wallet.WALLET_VERSION_V3,
1546
                            null,
1547
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1548
                            options.storeDataOnServer ? options.encryptedSecret : null,
1549
                            {keyIndex: options.primaryPublicKey},
1550
                            options.backupPublicKey,
1551
                            blocktrailPublicKeys,
1552
                            keyIndex,
1553
                            result.segwit || 0,
1554
                            self.testnet,
1555
                            checksum,
1556
                            result.upgrade_key_index,
1557
                            options.useCashAddress,
1558
                            options.bypassNewAddressCheck
1559
                        );
1560
1561
                        // pass along decrypted data to avoid extra work
1562
                        return wallet.unlock({
1563
                            walletVersion: Wallet.WALLET_VERSION_V3,
1564
                            passphrase: options.passphrase,
1565
                            primarySeed: options.primarySeed,
1566
                            secret: options.secret
1567
                        }).then(function() {
1568
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1569
                            return [
1570
                                wallet,
1571
                                {
1572
                                    walletVersion: wallet.walletVersion,
1573
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1574
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1575
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1576
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1577
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1578
                                    blocktrailPublicKeys: blocktrailPublicKeys
1579
                                }
1580
                            ];
1581
                        });
1582
                    }
1583
                );
1584
        })
1585
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1586
1587
    return deferred.promise;
1588
};
1589
1590
function verifyPublicBip32Key(bip32Key, network) {
1591
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1592
    if (typeof hk.keyPair.d !== "undefined") {
1593
        throw new Error('BIP32Key contained private key material - abort');
1594
    }
1595
1596
    if (bip32Key[1].slice(0, 1) !== "M") {
1597
        throw new Error("BIP32Key contained non-public path - abort");
1598
    }
1599
}
1600
1601
function verifyPublicOnly(walletData, network) {
1602
    verifyPublicBip32Key(walletData.primary_public_key, network);
1603
    verifyPublicBip32Key(walletData.backup_public_key, network);
1604
}
1605
1606
/**
1607
 * create wallet using the API
1608
 *
1609
 * @param identifier            string      the wallet identifier to create
1610
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1611
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1612
 * @param primaryMnemonic       string      mnemonic to store
1613
 * @param checksum              string      checksum to store
1614
 * @param keyIndex              int         keyIndex that was used to create wallet
1615
 * @param segwit                bool
1616
 * @returns {q.Promise}
1617
 */
1618
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1619
                                                checksum, keyIndex, segwit) {
1620
    var self = this;
1621
1622
    var postData = {
1623
        identifier: identifier,
1624
        wallet_version: Wallet.WALLET_VERSION_V1,
1625
        primary_public_key: primaryPublicKey,
1626
        backup_public_key: backupPublicKey,
1627
        primary_mnemonic: primaryMnemonic,
1628
        checksum: checksum,
1629
        key_index: keyIndex,
1630
        segwit: segwit
1631
    };
1632
1633
    verifyPublicOnly(postData, self.network);
1634
1635
    return self.client.post("/wallet", null, postData);
1636
};
1637
1638
/**
1639
 * create wallet using the API
1640
 *
1641
 * @param identifier            string      the wallet identifier to create
1642
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1643
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1644
 * @param encryptedPrimarySeed  string      openssl format
1645
 * @param encryptedSecret       string      openssl format
1646
 * @param recoverySecret        string      openssl format
1647
 * @param checksum              string      checksum to store
1648
 * @param keyIndex              int         keyIndex that was used to create wallet
1649
 * @param supportSecret         string
1650
 * @param segwit                bool
1651
 * @returns {q.Promise}
1652
 */
1653
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1654
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1655
    var self = this;
1656
1657
    var postData = {
1658
        identifier: identifier,
1659
        wallet_version: Wallet.WALLET_VERSION_V2,
1660
        primary_public_key: primaryPublicKey,
1661
        backup_public_key: backupPublicKey,
1662
        encrypted_primary_seed: encryptedPrimarySeed,
1663
        encrypted_secret: encryptedSecret,
1664
        recovery_secret: recoverySecret,
1665
        checksum: checksum,
1666
        key_index: keyIndex,
1667
        support_secret: supportSecret || null,
1668
        segwit: segwit
1669
    };
1670
1671
    verifyPublicOnly(postData, self.network);
1672
1673
    return self.client.post("/wallet", null, postData);
1674
};
1675
1676
/**
1677
 * create wallet using the API
1678
 *
1679
 * @param identifier            string      the wallet identifier to create
1680
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1681
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1682
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1683
 * @param encryptedSecret       Buffer      buffer of ciphertext
1684
 * @param recoverySecret        Buffer      buffer of recovery secret
1685
 * @param checksum              string      checksum to store
1686
 * @param keyIndex              int         keyIndex that was used to create wallet
1687
 * @param supportSecret         string
1688
 * @param segwit                bool
1689
 * @returns {q.Promise}
1690
 */
1691
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1692
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1693
    var self = this;
1694
1695
    var postData = {
1696
        identifier: identifier,
1697
        wallet_version: Wallet.WALLET_VERSION_V3,
1698
        primary_public_key: primaryPublicKey,
1699
        backup_public_key: backupPublicKey,
1700
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1701
        encrypted_secret: encryptedSecret.toString('base64'),
1702
        recovery_secret: recoverySecret.toString('hex'),
1703
        checksum: checksum,
1704
        key_index: keyIndex,
1705
        support_secret: supportSecret || null,
1706
        segwit: segwit
1707
    };
1708
1709
    verifyPublicOnly(postData, self.network);
1710
1711
    return self.client.post("/wallet", null, postData);
1712
};
1713
1714
/**
1715
 * create wallet using the API
1716
 *
1717
 * @param identifier            string      the wallet identifier to create
1718
 * @param postData              object
1719
 * @param [cb]                  function    callback(err, result)
1720
 * @returns {q.Promise}
1721
 */
1722
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1723
    var self = this;
1724
1725
    return self.client.post("/wallet/" + identifier, null, postData, cb);
1726
};
1727
1728
/**
1729
 * upgrade wallet to use a new account number
1730
 *  the account number specifies which blocktrail cosigning key is used
1731
 *
1732
 * @param identifier            string      the wallet identifier
1733
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1734
 * @param keyIndex              int         keyIndex that was used to create wallet
1735
 * @param [cb]                  function    callback(err, result)
1736
 * @returns {q.Promise}
1737
 */
1738
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1739
    var self = this;
1740
1741
    return self.client.post("/wallet/" + identifier + "/upgrade", null, {
1742
        key_index: keyIndex,
1743
        primary_public_key: primaryPublicKey
1744
    }, cb);
1745
};
1746
1747
/**
1748
 * get the balance for the wallet
1749
 *
1750
 * @param identifier            string      the wallet identifier
1751
 * @param [cb]                  function    callback(err, result)
1752
 * @returns {q.Promise}
1753
 */
1754
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1755
    var self = this;
1756
1757
    return self.client.get("/wallet/" + identifier + "/balance", null, true, cb);
1758
};
1759
1760
/**
1761
 * do HD wallet discovery for the wallet
1762
 *
1763
 * @param identifier            string      the wallet identifier
1764
 * @param [cb]                  function    callback(err, result)
1765
 * @returns {q.Promise}
1766
 */
1767
APIClient.prototype.doWalletDiscovery = function(identifier, gap, cb) {
1768
    var self = this;
1769
1770
    return self.client.get("/wallet/" + identifier + "/discovery", {gap: gap}, true, cb);
1771
};
1772
1773
1774
/**
1775
 * get a new derivation number for specified parent path
1776
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1777
 *
1778
 * @param identifier            string      the wallet identifier
1779
 * @param path                  string      the parent path for which to get a new derivation,
1780
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1781
 * @param [cb]                  function    callback(err, result)
1782
 * @returns {q.Promise}
1783
 */
1784
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1785
    var self = this;
1786
1787
    return self.client.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1788
};
1789
1790
1791
/**
1792
 * delete the wallet
1793
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1794
 *  is required to be able to delete a wallet
1795
 *
1796
 * @param identifier            string      the wallet identifier
1797
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1798
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1799
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1800
 * @param [cb]                  function    callback(err, result)
1801
 * @returns {q.Promise}
1802
 */
1803
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1804
    var self = this;
1805
1806
    if (typeof force === "function") {
1807
        cb = force;
1808
        force = false;
1809
    }
1810
1811
    return self.client.delete("/wallet/" + identifier, {force: force}, {
1812
        checksum: checksumAddress,
1813
        signature: checksumSignature
1814
    }, cb);
1815
};
1816
1817
/**
1818
 * use the API to get the best inputs to use based on the outputs
1819
 *
1820
 * the return array has the following format:
1821
 * [
1822
 *  "utxos" => [
1823
 *      [
1824
 *          "hash" => "<txHash>",
1825
 *          "idx" => "<index of the output of that <txHash>",
1826
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1827
 *          "value" => 32746327,
1828
 *          "address" => "1address",
1829
 *          "path" => "m/44'/1'/0'/0/13",
1830
 *          "redeem_script" => "<redeemScript-hex>",
1831
 *      ],
1832
 *  ],
1833
 *  "fee"   => 10000,
1834
 *  "change"=> 1010109201,
1835
 * ]
1836
 *
1837
 * @param identifier        string      the wallet identifier
1838
 * @param pay               array       {'address': (int)value}     coins to send
1839
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1840
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1841
 * @param feeStrategy       string      defaults to
1842
 * @param options
1843
 * @param [cb]              function    callback(err, utxos, fee, change)
1844
 * @returns {q.Promise}
1845
 */
1846
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1847
    var self = this;
1848
1849
    if (typeof feeStrategy === "function") {
1850
        cb = feeStrategy;
1851
        feeStrategy = null;
1852
        options = {};
1853
    } else if (typeof options === "function") {
1854
        cb = options;
1855
        options = {};
1856
    }
1857
1858
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1859
    options = options || {};
1860
1861
    var deferred = q.defer();
1862
    deferred.promise.spreadNodeify(cb);
1863
1864
    var params = {
1865
        lock: lockUTXO,
1866
        zeroconf: allowZeroConf ? 1 : 0,
1867
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1868
        fee_strategy: feeStrategy
1869
    };
1870
1871
    if (options.forcefee) {
1872
        params['forcefee'] = options.forcefee;
1873
    }
1874
1875
    deferred.resolve(
1876
        self.client.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1877
            function(result) {
1878
                return [result.utxos, result.fee, result.change, result];
1879
            },
1880
            function(err) {
1881
                if (err.message.match(/too low to pay the fee/)) {
1882
                    throw blocktrail.WalletFeeError(err);
1883
                }
1884
1885
                throw err;
1886
            }
1887
        )
1888
    );
1889
1890
    return deferred.promise;
1891
};
1892
1893
/**
1894
 * @param [cb]              function    callback(err, utxos, fee, change)
1895
 * @returns {q.Promise}
1896
 */
1897
APIClient.prototype.feePerKB = function(cb) {
1898
    var self = this;
1899
1900
    var deferred = q.defer();
1901
    deferred.promise.spreadNodeify(cb);
1902
1903
    deferred.resolve(self.client.get("/fee-per-kb"));
1904
1905
    return deferred.promise;
1906
};
1907
1908
/**
1909
 * send the transaction using the API
1910
 *
1911
 * @param identifier        string      the wallet identifier
1912
 * @param txHex             string      partially signed transaction as hex string
1913
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1914
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1915
 * @param [twoFactorToken]  string      2FA token
1916
 * @param [prioboost]       bool
1917
 * @param [cb]              function    callback(err, txHash)
1918
 * @returns {q.Promise}
1919
 */
1920
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1921
    var self = this;
1922
1923
    if (typeof twoFactorToken === "function") {
1924
        cb = twoFactorToken;
1925
        twoFactorToken = null;
1926
        prioboost = false;
1927
    } else if (typeof prioboost === "function") {
1928
        cb = prioboost;
1929
        prioboost = false;
1930
    }
1931
1932
    var data = {
1933
        paths: paths,
1934
        two_factor_token: twoFactorToken
1935
    };
1936
    if (typeof txHex === "string") {
1937
        data.raw_transaction = txHex;
1938
    } else if (typeof txHex === "object") {
1939
        Object.keys(txHex).map(function(key) {
1940
            data[key] = txHex[key];
1941
        });
1942
    }
1943
1944
    return self.client.post(
1945
        "/wallet/" + identifier + "/send",
1946
        {
1947
            check_fee: checkFee ? 1 : 0,
1948
            prioboost: prioboost ? 1 : 0
1949
        },
1950
        data,
1951
        cb
1952
    );
1953
};
1954
1955
/**
1956
 * setup a webhook for this wallet
1957
 *
1958
 * @param identifier        string      the wallet identifier
1959
 * @param webhookIdentifier string      identifier for the webhook
1960
 * @param url               string      URL to receive webhook events
1961
 * @param [cb]              function    callback(err, webhook)
1962
 * @returns {q.Promise}
1963
 */
1964
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
1965
    var self = this;
1966
1967
    return self.client.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
1968
};
1969
1970
/**
1971
 * delete a webhook that was created for this wallet
1972
 *
1973
 * @param identifier        string      the wallet identifier
1974
 * @param webhookIdentifier string      identifier for the webhook
1975
 * @param [cb]              function    callback(err, success)
1976
 * @returns {q.Promise}
1977
 */
1978
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
1979
    var self = this;
1980
1981
    return self.client.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
1982
};
1983
1984
/**
1985
 * get all transactions for an wallet (paginated)
1986
 *
1987
 * @param identifier    string      wallet identifier
1988
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1989
 * @param [cb]          function    callback function to call when request is complete
1990
 * @return q.Promise
1991
 */
1992
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
1993
    var self = this;
1994
1995
    if (typeof params === "function") {
1996
        cb = params;
1997
        params = null;
1998
    }
1999
2000
    return self.client.get("/wallet/" + identifier + "/transactions", params, true, cb);
2001
};
2002
2003
/**
2004
 * get all addresses for an wallet (paginated)
2005
 *
2006
 * @param identifier    string      wallet identifier
2007
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2008
 * @param [cb]          function    callback function to call when request is complete
2009
 * @return q.Promise
2010
 */
2011
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
2012
    var self = this;
2013
2014
    if (typeof params === "function") {
2015
        cb = params;
2016
        params = null;
2017
    }
2018
2019
    return self.client.get("/wallet/" + identifier + "/addresses", params, true, cb);
2020
};
2021
2022
/**
2023
 * @param identifier    string      wallet identifier
2024
 * @param address       string      the address to label
2025
 * @param label         string      the label
2026
 * @param [cb]          function    callback(err, res)
2027
 * @return q.Promise
2028
 */
2029
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
2030
    var self = this;
2031
2032
    return self.client.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
2033
};
2034
2035
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
2036
    var self = this;
2037
2038
    if (typeof feeStrategy === "function") {
2039
        cb = feeStrategy;
2040
        feeStrategy = null;
2041
    } else if (typeof options === "function") {
2042
        cb = options;
2043
        options = {};
2044
    }
2045
2046
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
2047
    options = options || {};
2048
2049
    var params = {
2050
        outputs: options.outputs ? options.outputs : 1,
2051
        zeroconf: allowZeroConf ? 1 : 0,
2052
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
2053
        fee_strategy: feeStrategy
2054
    };
2055
2056
    if (options.forcefee) {
2057
        params['forcefee'] = options.forcefee;
2058
    }
2059
2060
    return self.client.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
2061
};
2062
2063
/**
2064
 * get all UTXOs for an wallet (paginated)
2065
 *
2066
 * @param identifier    string      wallet identifier
2067
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
2068
 * @param [cb]          function    callback function to call when request is complete
2069
 * @return q.Promise
2070
 */
2071
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
2072
    var self = this;
2073
2074
    if (typeof params === "function") {
2075
        cb = params;
2076
        params = null;
2077
    }
2078
2079
    return self.client.get("/wallet/" + identifier + "/utxos", params, true, cb);
2080
};
2081
2082
/**
2083
 * get a paginated list of all wallets associated with the api user
2084
 *
2085
 * @param [params]      object      pagination: {page: 1, limit: 20}
2086
 * @param [cb]          function    callback function to call when request is complete
2087
 * @return q.Promise
2088
 */
2089
APIClient.prototype.allWallets = function(params, cb) {
2090
    var self = this;
2091
2092
    if (typeof params === "function") {
2093
        cb = params;
2094
        params = null;
2095
    }
2096
2097
    return self.client.get("/wallets", params, true, cb);
2098
};
2099
2100
/**
2101
 * verify a message signed bitcoin-core style
2102
 *
2103
 * @param message        string
2104
 * @param address        string
2105
 * @param signature      string
2106
 * @param [cb]          function    callback function to call when request is complete
2107
 * @return q.Promise
2108
 */
2109
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2110
    var self = this;
2111
2112
    // we could also use the API instead of the using bitcoinjs-lib to verify
2113
    // return self.client.post("/verify_message", null, {message: message, address: address, signature: signature}, cb);
2114
2115
    var deferred = q.defer();
2116
    deferred.promise.nodeify(cb);
2117
    try {
2118
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2119
        deferred.resolve(result);
2120
    } catch (e) {
2121
        deferred.reject(e);
2122
    }
2123
2124
    return deferred.promise;
2125
};
2126
2127
/**
2128
 * max is 0.001
2129
 * testnet only
2130
 *
2131
 * @param address
2132
 * @param amount
2133
 * @param cb
2134
 */
2135
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2136
    var self = this;
2137
2138
    return self.client.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2139
};
2140
2141
/**
2142
 * send a raw transaction
2143
 *
2144
 * @param rawTransaction    string      raw transaction as HEX
2145
 * @param [cb]              function    callback function to call when request is complete
2146
 * @return q.Promise
2147
 */
2148
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2149
    var self = this;
2150
2151
    return self.client.post("/send-raw-tx", null, rawTransaction, cb);
2152
};
2153
2154
/**
2155
 * get the current price index
2156
 *
2157
 * @param [cb]          function    callback({'USD': 287.30})
2158
 * @return q.Promise
2159
 */
2160
APIClient.prototype.price = function(cb) {
2161
    var self = this;
2162
2163
    return self.client.get("/price", null, false, cb);
2164
};
2165
2166
module.exports = APIClient;
2167